package com.im.project.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.im.common.component.ThreadPoolExecutorHandler;
import com.im.common.constant.Constant;
import com.im.common.enums.MessageTypeEnum;
import com.im.common.enums.ResultEnum;
import com.im.common.enums.WsEnum;
import com.im.common.exception.BusinessException;
import com.im.common.util.GenerateIdUtil;
import com.im.common.util.StringTool;
import com.im.project.entity.ChatMessage;
import com.im.project.entity.Cluster;
import com.im.project.entity.dto.MessageTopDto;
import com.im.project.entity.dto.SendMessageDto;
import com.im.project.entity.dto.UpdateChatUserMessageDto;
import com.im.project.entity.vo.MessageVo;
import com.im.project.entity.vo.ReplyMessageVo;
import com.im.project.entity.vo.UserVo;
import com.im.project.mapper.ChatMessageMapper;
import com.im.project.rabbitmq.RabbitmqProducer;
import com.im.project.service.ChatMessageService;
import com.im.project.webSocket.UserChannelCtxMap;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 会话消息 服务层接口
 *
 * @author yunZhouJiaDi
 */
@Slf4j
@Service
public class ChatMessageServiceImpl extends ServiceImpl<ChatMessageMapper, ChatMessage> implements ChatMessageService {

    @Resource
    private ChatMessageMapper chatMessageMapper;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private RabbitmqProducer rabbitmqProducer;

    @Override
    public Boolean sendPrivateMessage(SendMessageDto sendMessageDto) {
        List<String> contactIdList = redisTemplate.opsForList().range(Constant.RedisConstant.USER_CONTACT + sendMessageDto.getSendUserId(), 0, -1);
        if (!contactIdList.contains(sendMessageDto.getContactId())) {
            throw new BusinessException(ResultEnum.NOT_FRIEND.getCode(), ResultEnum.NOT_FRIEND.getMessage());
        }
        if (sendMessageDto.getMessageType().equalsIgnoreCase(MessageTypeEnum.WITHDRAW.getName())) {
            withdrawMessage(sendMessageDto.getMessageType(), sendMessageDto.getWithdrawId());
        }
        ReplyMessageVo replyMessageVo = null;
        if (sendMessageDto.getMessageType().equalsIgnoreCase(MessageTypeEnum.REPLY.getName())) {
            replyMessageVo = replyMessage(sendMessageDto.getMessageType(), sendMessageDto.getReplyId());
        }
        UserVo sendUser = (UserVo) redisTemplate.opsForValue().get(Constant.RedisConstant.USER + sendMessageDto.getSendUserId());
        UserVo contactUser = (UserVo) redisTemplate.opsForValue().get(Constant.RedisConstant.USER + sendMessageDto.getContactId());
        String chatId = GenerateIdUtil.generateUserChatId(new String[]{sendMessageDto.getSendUserId(), sendMessageDto.getContactId()});
        Integer contactType = ResultEnum.FRIEND_TYPE.getCode();
        String messageId = GenerateIdUtil.generateMessageId();
        Long currentTime = System.currentTimeMillis();
        ChatMessage chatMessage = new ChatMessage().
                setChatId(chatId).
                setMessageId(messageId).
                setMessageType(sendMessageDto.getMessageType()).
                setMessageContent(StringTool.escape(sendMessageDto.getMessageContent())).
                setContactType(contactType).
                setSendUserId(sendMessageDto.getSendUserId()).
                setSendUsername(sendUser.getUsername()).
                setSendUserIcon(sendUser.getIcon()).
                setSendTime(currentTime).
                setContactId(sendMessageDto.getContactId()).
                setContactUsername(contactUser.getUsername()).
                setContactUserIcon(contactUser.getIcon()).
                setFileSize(sendMessageDto.getFileSize()).
                setFileName(sendMessageDto.getFileName()).
                setWithdrawId(sendMessageDto.getWithdrawId()).
                setReplyId(sendMessageDto.getReplyId()).
                setReplyMessageType(replyMessageVo == null ? "" : replyMessageVo.getReplyMessageType()).
                setReplyContent(replyMessageVo == null ? "" : replyMessageVo.getReplyContent()).
                setReplyName(replyMessageVo == null ? "" : replyMessageVo.getReplyName()).
                setReplyFileSize(replyMessageVo == null ? 0 : replyMessageVo.getReplyFileSize()).
                setMsgTop(ResultEnum.NOT_TOP_MESSAGE.getCode());
        ChatMessage redisSendMessageVo = (ChatMessage) redisTemplate.opsForValue().get(Constant.RedisConstant.MESSAGE_DEDUPLICATION + sendMessageDto.getDeduplicationId());
        if (!ObjectUtils.isEmpty(redisSendMessageVo)) {
            ThreadPoolExecutorHandler.threadPoolExecutor.execute(() -> {
                rabbitmqProducer.sendMessageProducer(Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                        new MessageVo<ChatMessage>().
                                setCommandCode(WsEnum.PRIVATE_CHAT_SEND_ACK.getCode()).
                                setCommandName(WsEnum.PRIVATE_CHAT_SEND_ACK.getMessage()).
                                setContactId(sendMessageDto.getSendUserId()).
                                setData(chatMessage)
                );
                rabbitmqProducer.sendMessageProducer(Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                        new MessageVo<ChatMessage>().
                                setCommandCode(WsEnum.PRIVATE_CHAT_SEND.getCode()).
                                setCommandName(WsEnum.PRIVATE_CHAT_SEND.getMessage()).
                                setContactId(sendMessageDto.getContactId()).
                                setData(chatMessage)
                );
            });
        } else {
            ThreadPoolExecutorHandler.threadPoolExecutor.execute(() -> {
                rabbitmqProducer.sendMessageProducer(
                        Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.UPDATE_MESSAGE,
                        new UpdateChatUserMessageDto().
                                setSendUserId(sendMessageDto.getSendUserId()).
                                setCurrentTime(currentTime).
                                setMessageType(sendMessageDto.getMessageType()).
                                setMessageContent(StringTool.escape(sendMessageDto.getMessageContent())).
                                setChatId(chatId)
                );
                rabbitmqProducer.sendMessageProducer(
                        Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SAVE_MESSAGE,
                        chatMessage
                );
                rabbitmqProducer.sendMessageProducer(Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                        new MessageVo<ChatMessage>().
                                setCommandCode(WsEnum.PRIVATE_CHAT_SEND_ACK.getCode()).
                                setCommandName(WsEnum.PRIVATE_CHAT_SEND_ACK.getMessage()).
                                setContactId(sendMessageDto.getSendUserId()).
                                setData(chatMessage)
                );
                rabbitmqProducer.sendMessageProducer(Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                        new MessageVo<ChatMessage>().
                                setCommandCode(WsEnum.PRIVATE_CHAT_SEND.getCode()).
                                setCommandName(WsEnum.PRIVATE_CHAT_SEND.getMessage()).
                                setContactId(sendMessageDto.getContactId()).
                                setData(chatMessage)
                );
                redisTemplate.opsForValue().set(Constant.RedisConstant.MESSAGE_DEDUPLICATION + sendMessageDto.getDeduplicationId(), chatMessage, 3L, TimeUnit.MINUTES);
            });
        }
        return true;
    }

    @Override
    @Transactional
    public Boolean sendClusterMessage(SendMessageDto sendMessageDto) {
        Cluster cluster = (Cluster) redisTemplate.opsForValue().get(Constant.RedisConstant.CLUSTER + sendMessageDto.getContactId());
        if (cluster.getMute() == ResultEnum.PROHIBITION.getCode()) {
            throw new BusinessException(ResultEnum.PROHIBITED_UNABLE_TO_SEND_MESSAGE.getCode(), ResultEnum.PROHIBITED_UNABLE_TO_SEND_MESSAGE.getMessage());
        }
        if (cluster.getStatus() == ResultEnum.CLUSTER_DISSOLUTION.getCode()) {
            throw new BusinessException(ResultEnum.CLUSTER_PART.getCode(), ResultEnum.CLUSTER_PART.getMessage());
        }
        List<String> contactIdList = redisTemplate.opsForList().range(Constant.RedisConstant.CLUSTER_USER + cluster.getClusterId(), 0, -1);
        if (!contactIdList.contains(sendMessageDto.getSendUserId())) {
            throw new BusinessException(ResultEnum.YOU_ARE_NOT_A_GROUP_MEMBER_AND_CANNOT_SEND_MESSAGES.getCode(), ResultEnum.YOU_ARE_NOT_A_GROUP_MEMBER_AND_CANNOT_SEND_MESSAGES.getMessage());
        }
        if (sendMessageDto.getMessageType().equalsIgnoreCase(MessageTypeEnum.WITHDRAW.getName())) {
            withdrawMessage(sendMessageDto.getMessageType(), sendMessageDto.getWithdrawId());
        }
        ReplyMessageVo replyMessageVo = null;
        if (sendMessageDto.getMessageType().equalsIgnoreCase(MessageTypeEnum.REPLY.getName())) {
            replyMessageVo = replyMessage(sendMessageDto.getMessageType(), sendMessageDto.getReplyId());
        }
        UserVo sendUser = (UserVo) redisTemplate.opsForValue().get(Constant.RedisConstant.USER + sendMessageDto.getSendUserId());
        String chatId = GenerateIdUtil.generateClusterChatId(sendMessageDto.getContactId());
        Integer contactType = ResultEnum.CLUSTER_TYPE.getCode();
        String messageId = GenerateIdUtil.generateMessageId();
        Long currentTime = System.currentTimeMillis();
        ChatMessage chatMessage = new ChatMessage().
                setChatId(chatId).
                setMessageId(messageId).
                setMessageType(sendMessageDto.getMessageType()).
                setMessageContent(StringTool.escape(sendMessageDto.getMessageContent())).
                setContactType(contactType).
                setSendUserId(sendMessageDto.getSendUserId()).
                setSendUsername(sendUser.getUsername()).
                setSendUserIcon(sendUser.getIcon()).
                setSendTime(currentTime).
                setContactId(sendMessageDto.getContactId()).
                setContactUsername(cluster.getName()).
                setContactUserIcon(cluster.getIcon()).
                setFileSize(sendMessageDto.getFileSize()).
                setFileName(sendMessageDto.getFileName()).
                setWithdrawId(sendMessageDto.getWithdrawId()).
                setReplyId(sendMessageDto.getReplyId()).
                setReplyMessageType(replyMessageVo == null ? "" : replyMessageVo.getReplyMessageType()).
                setReplyContent(replyMessageVo == null ? "" : replyMessageVo.getReplyContent()).
                setReplyName(replyMessageVo == null ? "" : replyMessageVo.getReplyName()).
                setReplyFileSize(replyMessageVo == null ? 0 : replyMessageVo.getReplyFileSize()).
                setMsgTop(ResultEnum.NOT_TOP_MESSAGE.getCode()).
                setMemberCount(contactIdList.size());
        ChatMessage redisSendMessageVo = (ChatMessage) redisTemplate.opsForValue().get(Constant.RedisConstant.MESSAGE_DEDUPLICATION + sendMessageDto.getDeduplicationId());
        if (!ObjectUtils.isEmpty(redisSendMessageVo)) {
            ThreadPoolExecutorHandler.threadPoolExecutor.execute(() -> {
                rabbitmqProducer.sendMessageProducer(
                        Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                        new MessageVo<ChatMessage>().
                                setCommandCode(WsEnum.CLUSTER_CHAT_SEND_ACK.getCode()).
                                setCommandName(WsEnum.CLUSTER_CHAT_SEND_ACK.getMessage()).
                                setContactId(sendMessageDto.getSendUserId()).
                                setData(chatMessage)
                );
                contactIdList.forEach(contactId -> {
                    if (!sendMessageDto.getSendUserId().equalsIgnoreCase(contactId)) {
                        rabbitmqProducer.sendMessageProducer(
                                Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                                new MessageVo<ChatMessage>().
                                        setCommandCode(WsEnum.CLUSTER_CHAT_SEND.getCode()).
                                        setCommandName(WsEnum.CLUSTER_CHAT_SEND.getMessage()).
                                        setContactId(contactId).
                                        setData(chatMessage)
                        );
                    }
                });
            });
        } else {
            ThreadPoolExecutorHandler.threadPoolExecutor.execute(() -> {
                rabbitmqProducer.sendMessageProducer(
                        Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.UPDATE_MESSAGE,
                        new UpdateChatUserMessageDto().
                                setSendUserId(sendMessageDto.getSendUserId()).
                                setCurrentTime(currentTime).
                                setMessageType(sendMessageDto.getMessageType()).
                                setMessageContent(StringTool.escape(sendMessageDto.getMessageContent())).
                                setLastSendName(sendUser.getUsername()).
                                setChatId(chatId)
                );
                rabbitmqProducer.sendMessageProducer(
                        Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SAVE_MESSAGE,
                        chatMessage
                );
                rabbitmqProducer.sendMessageProducer(
                        Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                        new MessageVo<ChatMessage>().
                                setCommandCode(WsEnum.CLUSTER_CHAT_SEND_ACK.getCode()).
                                setCommandName(WsEnum.CLUSTER_CHAT_SEND_ACK.getMessage()).
                                setContactId(sendMessageDto.getSendUserId()).
                                setData(chatMessage)
                );
                contactIdList.forEach(contactId -> {
                    if (!sendMessageDto.getSendUserId().equalsIgnoreCase(contactId)) {
                        rabbitmqProducer.sendMessageProducer(
                                Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                                new MessageVo<ChatMessage>().
                                        setCommandCode(WsEnum.CLUSTER_CHAT_SEND.getCode()).
                                        setCommandName(WsEnum.CLUSTER_CHAT_SEND.getMessage()).
                                        setContactId(contactId).
                                        setData(chatMessage)
                        );
                    }
                });
                redisTemplate.opsForValue().set(Constant.RedisConstant.MESSAGE_DEDUPLICATION + sendMessageDto.getDeduplicationId(), chatMessage, 3L, TimeUnit.MINUTES);
            });
        }
        return true;
    }

    @Override
    public Boolean updateReadMsgState(String contactId) {
        String currentUserId = StpUtil.getLoginIdAsString();
        String chatId = GenerateIdUtil.generateUserChatId(new String[]{StpUtil.getLoginIdAsString(), contactId});
        Channel channel = UserChannelCtxMap.getContext(contactId);
        if (channel == null) {
            List<String> contactReadList = (List<String>) redisTemplate.opsForList().range(Constant.RedisConstant.READ_CHAT_MESSAGE + contactId, 0, -1);
            if (contactReadList.size() > 0) {
                if (!contactReadList.contains(currentUserId)) {
                    redisTemplate.opsForList().leftPush(Constant.RedisConstant.READ_CHAT_MESSAGE + contactId, currentUserId);
                }
            } else {
                redisTemplate.opsForList().leftPush(Constant.RedisConstant.READ_CHAT_MESSAGE + contactId, currentUserId);
            }
        } else {
            rabbitmqProducer.sendMessageProducer(Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                    new MessageVo<>().
                            setCommandCode(WsEnum.PRIVATE_MESSAGE_READ.getCode()).
                            setCommandName(WsEnum.PRIVATE_MESSAGE_READ.getMessage()).
                            setContactId(contactId).
                            setData(chatId)
            );
        }
        return true;
    }

    @Override
    @Transactional
    public Boolean setMessageTop(MessageTopDto messageTopDto) {
        chatMessageMapper.update(
                new ChatMessage().
                        setMsgTop(ResultEnum.NOT_TOP_MESSAGE.getCode()),
                new QueryWrapper<ChatMessage>().
                        eq("msg_top", ResultEnum.TOP_MESSAGE.getCode())
        );
        chatMessageMapper.update(
                new ChatMessage().
                        setMsgTop(ResultEnum.TOP_MESSAGE.getCode()),
                new QueryWrapper<ChatMessage>().
                        eq("message_id", messageTopDto.getMessageId())
        );
        ChatMessage chatMessage = getOne(new QueryWrapper<ChatMessage>().eq("message_id", messageTopDto.getMessageId()));
        Integer contactType = messageTopDto.getContactType();
        if (contactType.equals(ResultEnum.FRIEND_TYPE.getCode())) {
            rabbitmqProducer.sendMessageProducer(
                    Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                    new MessageVo<ChatMessage>().
                            setCommandCode(WsEnum.PRIVATE_MESSAGE_TOP.getCode()).
                            setCommandName(WsEnum.PRIVATE_MESSAGE_TOP.getMessage()).
                            setContactId(messageTopDto.getContactId()).
                            setData(chatMessage)
            );
        } else if (contactType.equals(ResultEnum.CLUSTER_TYPE.getCode())) {
            List<String> contactIdList = redisTemplate.opsForList().range(Constant.RedisConstant.CLUSTER_USER + messageTopDto.getContactId(), 0, -1);
            contactIdList.forEach(contactId -> {
                if (!StpUtil.getLoginIdAsString().equalsIgnoreCase(contactId)) {
                    rabbitmqProducer.sendMessageProducer(
                            Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                            new MessageVo<ChatMessage>().
                                    setCommandCode(WsEnum.CLUSTER_MESSAGE_TOP.getCode()).
                                    setCommandName(WsEnum.CLUSTER_MESSAGE_TOP.getMessage()).
                                    setContactId(contactId).
                                    setData(chatMessage)
                    );
                }
            });
        }
        return true;
    }

    private void withdrawMessage(String messageType, String withdrawId) {
        if (messageType.equalsIgnoreCase(MessageTypeEnum.WITHDRAW.getName())) {
            ChatMessage chatMessage = chatMessageMapper.selectOne(new QueryWrapper<ChatMessage>().eq("message_id", withdrawId));
            if (!StpUtil.getLoginIdAsString().equalsIgnoreCase(chatMessage.getSendUserId())) {
                throw new BusinessException(ResultEnum.THIS_MESSAGE_WAS_NOT_SENT_BY_YOU_AND_CANNOT_BE_RETRACTED.getCode(), ResultEnum.THIS_MESSAGE_WAS_NOT_SENT_BY_YOU_AND_CANNOT_BE_RETRACTED.getMessage());
            }
            if (((System.currentTimeMillis() - chatMessage.getSendTime()) / 1000) > 120) {
                throw new BusinessException(ResultEnum.THE_MESSAGE_WAS_SENT_MORE_THAN_FIVE_MINUTES_AGO_AND_CANNOT_BE_RETRACTED.getCode(), ResultEnum.THE_MESSAGE_WAS_SENT_MORE_THAN_FIVE_MINUTES_AGO_AND_CANNOT_BE_RETRACTED.getMessage());
            }
            chatMessage.setMsgWithdrawn(ResultEnum.MSG_WITHDRAWN.getCode());
            chatMessageMapper.updateById(chatMessage);
        }
    }

    private ReplyMessageVo replyMessage(String messageType, String replyId) {
        ReplyMessageVo replyMessageVo = null;
        if (messageType.equalsIgnoreCase(MessageTypeEnum.REPLY.getName())) {
            ChatMessage chatMessage = getOne(new QueryWrapper<ChatMessage>().eq("message_id", replyId));
            UserVo sendUser = (UserVo) redisTemplate.opsForValue().get(Constant.RedisConstant.USER + chatMessage.getSendUserId());
            if (!ObjectUtils.isEmpty(chatMessage)) {
                replyMessageVo = new ReplyMessageVo().
                        setReplyContent(chatMessage.getMessageContent()).
                        setReplyName(sendUser.getUsername()).
                        setReplyMessageType(chatMessage.getMessageType()).
                        setReplyFileSize(chatMessage.getFileSize() == null ? 0 : chatMessage.getFileSize());
            } else {
                replyMessageVo = new ReplyMessageVo();
            }
        }
        return replyMessageVo;
    }

}
